using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Configuration;
namespace SharpSoft.Web
{
    using Microsoft.AspNetCore.Authentication;
    using Microsoft.AspNetCore.Authentication.Cookies;
    using Microsoft.AspNetCore.DataProtection;
    using Microsoft.AspNetCore.Mvc;
    using Microsoft.AspNetCore.Mvc.ApplicationModels;
    using Microsoft.AspNetCore.Routing;
    using Microsoft.Extensions.DependencyInjection.Extensions;
    using Microsoft.Extensions.Hosting;
    using Microsoft.Extensions.Logging;
    using Microsoft.Extensions.Options;
    using Newtonsoft.Json;
    using Newtonsoft.Json.Linq;
    using Newtonsoft.Json.Serialization;
    using SharpSoft.Data;
    using SharpSoft.Data.DAOS;
    using System.IO;
    using System.Security.Claims;
    using System.Text.Encodings.Web;
    using System.Reflection;
    using MySql.Data.MySqlClient;

    public abstract class Startup
    {
        static Startup()
        {
            Startup.CookieExpiration = TimeSpan.FromMinutes(30);
        }
        private wsMsgService wsMsgSer = new wsMsgService();

        protected readonly IWebHostEnvironment HostEnvironment;
        public Startup(IWebHostEnvironment hostingEnvironment, IConfiguration cfg)
        {
            this.AuthenticationSchemes = new Dictionary<string, Action<CookieAuthenticationOptions>>();

            this.Configuration = cfg;
            this.HostEnvironment = hostingEnvironment;
            this.DefaultCultureInfo();

        }


        #region SettingByCodeCompiled
        /// <summary>
        /// 设置默认的Cookie过期时间
        /// </summary>
        public static TimeSpan? CookieExpiration { get; set; }

        /// <summary>
        /// 定义系统根账号名称（系统根账号可以跳过权限检查，默认拥有所有访问权。）
        /// </summary>
        public static string RootUserName { get; set; } = "root";
        /// <summary>
        /// 是否启用系统根账号
        /// </summary>
        public static bool EnableRootUser { get; set; } = false;
        /// <summary>
        /// 使用使用Razor的Pages
        /// </summary>
        public static bool UseRazorPages { get; set; } = true;
        /// <summary>
        /// 是否使用页面静态化服务
        /// </summary>
        public static bool ApplyStaticizeService { get; set; } = false;
        /// <summary>
        /// 是否允许跨域访问
        /// </summary>
        protected static bool AllowCrossOrigin { get; set; } = false;


        /// <summary>
        /// 在派生类中重写设置允许的跨域为null表示*
        /// </summary>
        protected static string[] AllowOrigins { get; set; } = null;

        /// <summary>
        /// 默认区域设置
        /// </summary>
        protected virtual void DefaultCultureInfo()
        {
            var ci = new System.Globalization.CultureInfo("zh-cn") { };
            ci.DateTimeFormat = new System.Globalization.DateTimeFormatInfo()
            {
                DateSeparator = "-",
                TimeSeparator = ":",
                FullDateTimePattern = "yyyy'-'MM'-'dd'_'HH':'mm':'ss",
                LongDatePattern = "yyyy'-'MM'-'dd",
                LongTimePattern = "HH':'mm':'ss",
                ShortDatePattern = "yyyy'-'MM'-'dd",
                ShortTimePattern = "HH':'mm':'ss",
                YearMonthPattern = "yyyy'-'MM",
                MonthDayPattern = "MM'-'dd",

            };
            System.Globalization.CultureInfo.CurrentCulture = ci;
            JsonConvert.DefaultSettings = () =>
            {
                var setting = new JsonSerializerSettings
                {
                    ContractResolver = new Newtonsoft.Json.Serialization.DefaultContractResolver(),
                    Formatting = Formatting.None,
                    DateFormatString = "yyyy'-'MM'-'dd' 'HH':'mm':'ss",

                };

                return setting;
            };
        }

        /// <summary>
        /// 应用配置
        /// </summary>
        public Microsoft.AspNetCore.Mvc.AppConfig AppConfig { get; set; }

        private IConfiguration Configuration { get; set; }

        #endregion

        #region 数据库
        /// <summary>
        /// 提供默认的数据连接
        /// </summary>
        /// <returns></returns>
        protected virtual Database SupplyDefaultDatabase(string connstr)
        {
            if (AppConfig?.dbType == null)
            {
                return null;
            }
            return Database.CreateInstance(AppConfig?.dbType, connstr);

        }

        /// <summary>
        /// 初始化数据架构
        /// </summary>
        /// <param name="database"></param>
        protected virtual void InitDatabaseSchemas(string connstr, Database database)
        {

        }
        /// <summary>
        /// 在系统启动时初始化数据架构完成之后初始化数据，比如系统角色表 ，默认账号之类的
        /// </summary>
        /// <param name="database"></param>
        protected virtual void InitData(Database database)
        {
            //this.SysConfig = new ConfigInDatabase(database);
            //var adal = new AccountsDAL(database, -1);
            //var roles = this.SysRoles;
            //if (roles != null)
            //{//插入角色定义
            //    foreach (var role in roles)
            //    {//创建缺失的系统角色
            //        if (adal.GetRole(role.Id) == null)
            //        {
            //            adal.InsertRole(role.Id, role.Name);
            //        }
            //    }
            //}
            ////创建根账号
            //var rootid = adal.GetUserIdByName(RootUserName);
            //if (!rootid.HasValue)
            //{
            //    adal.InsertAccount(RootUserName, SharpSoft.Security.MD5Hash("!01234567"), 0);
            //    ConsoleHelper.WriteLine($"已创建系统根账号:[{RootUserName}],密码为：[!01234567]，请注意保护数据安全，在发布版本中移除根账号或修改其密码。", ConsoleColor.Red);
            //}
        }

        ///// <summary>
        ///// 创建数据库（目前仅支持mysql）
        ///// </summary>
        ///// <param name="initConnStr"></param>
        ///// <param name="database"></param>
        ///// <param name="dbName"></param>
        //protected void CreateDatabaseSchema(string initConnStr, Database database, string dbName)
        //{
        //    if (!(database is MySql))
        //    {
        //        throw new Exception("CreateDatabase方法目前仅支持Mysql数据库。");
        //    }
        //    database.ExecuteNonQuery($"create schema if not exists `{dbName}`");
        //    MySqlConnectionStringBuilder b = new MySqlConnectionStringBuilder(initConnStr);
        //    b.Database = dbName;
        //    database.Close();
        //    database.ConnectionString = b.ToString();
        //}


        /// <summary>
        /// 储存在数据库中的系统配置信息
        /// </summary>
        protected ConfigInDatabase SysConfig { get; private set; }

        /*
        /// <summary>
        /// 使用指定的枚举类型定义系统的角色
        /// </summary>
        /// <typeparam name="T"></typeparam>
        protected void UseRoleDefineEnum<T>() where T : Enum
        {
            UseRoleDefineEnum(typeof(T));
        }
        /// <summary>
        /// 使用指定的枚举类型定义系统的角色
        /// </summary>
        /// <param name="roleDefineType"></param>
        protected void UseRoleDefineEnum(Type roleDefineType)
        {
            if (roleDefineType == null)
            {
                throw new ArgumentNullException(nameof(roleDefineType));
            }
            if (!roleDefineType.IsEnum)
            {
                throw new Exception("角色定义类型必须是枚举类型。");
            }
            if (Enum.GetUnderlyingType(roleDefineType) != typeof(int))
            {
                throw new Exception("角色定义枚举必须是32位整数。");
            }

            var ids = Enum.GetValues(roleDefineType);

            var fields = roleDefineType.GetFields(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static);

            List<AccountsDAL.Role> roles = new List<AccountsDAL.Role>();
            foreach (var field in fields)
            {
                var des = field.Name;
                var id = (int)field.GetValue(null);

                var da = field.GetCustomAttribute<System.ComponentModel.DescriptionAttribute>();
                if (da != null)
                {
                    des = da.Description;
                }
                roles.Add(new AccountsDAL.Role(id, des));

            }
            this.SysRoles = roles.ToArray();
        }

        /// <summary>
        /// 在派生类中实现，设置系统内置的角色
        /// </summary>
        protected virtual AccountsDAL.Role[] SysRoles { get; private set; } = null;
        */

        /// <summary>
        /// 初始化连接字符串
        /// </summary>
        /// <param name="connstr"></param>
        /// <returns></returns>
        protected virtual string InitConnectionString(string connstr)
        {
            return connstr;
        }
        #endregion

        #region 用户认证

        /// <summary>
        /// 为系统指定多个认证架构
        /// </summary>
        public Dictionary<string, Action<CookieAuthenticationOptions>> AuthenticationSchemes { get; private set; }

        /// <summary>
        /// 创建默认的Cookie认证选项
        /// </summary>
        /// <returns></returns>
        private void _DefaultCookieAuthenticationOptions(CookieAuthenticationOptions o)
        {

            o.LoginPath = new PathString("/Account/Login");
            o.LogoutPath = new PathString("/Account/Logout");
            o.AccessDeniedPath = new PathString("/Account/AccessDenied");//拒绝访问

            o.ReturnUrlParameter = "ReturnUrl";

            //o.TicketDataFormat =new TicketDataFormat(Microsoft.AspNetCore.DataProtection.IDataProtector);
            o.CookieManager = new ChunkingCookieManager();//***********CORE 3.0 CookieManager和Cookie.Name不再提供默认值，手动指定。*************
            o.Cookie.Name = $"{AuthorizationService.DefaultAuthenticationScheme}";
            o.ExpireTimeSpan = Startup.CookieExpiration.Value;
            //o.Cookie.Expiration = Startup.CookieExpiration;
            //CORE 3.0不允许使用Cookie.Expiration设置Cookie超时（该设置仅作用于浏览器，为了安全使用CookieAuthenticationOptions.ExpireTimeSpan控制Cookie在服务端的生命周期）
            if (Startup.AllowCrossOrigin)
            {//*******************这两个设置对跨域的Cookie很重要*******************
                o.Cookie.HttpOnly = false;//设置js可以读取cookie
                o.Cookie.SameSite = SameSiteMode.None;//允许跨域时不指定same-site
            }
            o.Validate();
        }
        #endregion

        /// <summary>
        /// 服务配置
        /// </summary>
        /// <param name="services"></param>
        public virtual void ConfigureServices(IServiceCollection services)
        {
            #region 数据库服务配置

            Database defdb = null;
            var appcfgSection = Configuration.GetSection("AppConfig");
            AppConfig = appcfgSection.Get<AppConfig>();

            if (AppConfig == null)
            {
                ConsoleHelper.Danger("appsettings.json中未定义AppConfig节点。");
            }

            services.AddOptions().Configure<AppConfig>(appc =>
            {
                Configuration.GetSection("AppConfig").Bind(appc);
            });

            ConsoleHelper.Info($"数据库类型：[{(AppConfig?.dbType ?? "未定义")}]");

            try
            {
                var connstr = InitConnectionString(AppConfig?.ConnectionString);
                defdb = this.SupplyDefaultDatabase(connstr);

                try
                {
                    InitDatabaseSchemas(connstr, defdb);
                }
                catch (Exception ex)
                {
                    ConsoleHelper.Danger("初始化数据架构时发生异常。" + ex.Message);
                }
                services.AddSingleton<DatabaseService>(new DatabaseService() { DefaultDatabase = defdb });

                try
                {
                    InitData(defdb);
                }
                catch (Exception ex)
                {
                    ConsoleHelper.Danger("初始化数据时发生异常。" + ex.Message);
                }
            }
            catch (Exception ex)
            {
                services.AddSingleton<DatabaseService>(new DatabaseService() { DefaultDatabase = null });
                ConsoleHelper.Danger("数据库配置异常。" + ex.Message);
            }
            #endregion

            #region 配置管理服务

            services.AddSingleton<ConfigBase>(new ConfigInDatabase(defdb));

            #endregion

            #region 配置允许同步IO

            services.Configure<Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServerOptions>(options =>
            {
                options.AllowSynchronousIO = true;
            });
            services.Configure<IISServerOptions>(options =>
            {
                options.AllowSynchronousIO = true;
            });
            #endregion

            //services.AddResponseCompression(options =>
            //{//动态压缩
            //    options.EnableForHttps = false;//可以使用控制通过安全连接的压缩的响应EnableForHttps选项，它默认处于禁用状态。 使用动态生成的页面压缩可能会导致安全问题如犯罪并违反攻击。
            //});


            services.AddTransient<IApplicationModelProvider, SharpSoft.Web.AuthorizeProvider>();//使用扩展的认证器

            services.AddSingleton<wsMsgService>(wsMsgSer);

            var mvc = services.AddMvc(options =>
            {
                options.EnableEndpointRouting = false;
                options.Filters.Add(typeof(APIExceptionFilter));
            });
            mvc.AddApplicationPart(typeof(Startup).Assembly);//将当前程序集当作应用的一部分以使用定义的通用Controller
            mvc.AddRazorRuntimeCompilation();//添加Razor运行时编译支持：package - Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation

            services.AddControllersWithViews(options =>
            {//添加控制器-视图支持 

            }).AddNewtonsoftJson(options =>
            {//添加JsonResult支持
                options.AllowInputFormatterExceptionMessages = false;//不提供详细的json解析异常
                options.SerializerSettings.ContractResolver = null;//不转换jsonresult的名称大小写
            })
            .SetCompatibilityVersion(Microsoft.AspNetCore.Mvc.CompatibilityVersion.Version_3_0);//设置兼容版本

            if (UseRazorPages)
            {
                services.AddRazorPages();
            }

            services.AddHttpContextAccessor();//Adds a default implementation for the Microsoft.AspNetCore.Http.IHttpContextAccessor service.
            services.AddSession();
            // Configure Auth 

            #region 权限策略

            //**********************************自定义权限检查策略*******************************************
            services.AddAuthorizationCore(options =>
            {
                options.AddPolicy(AuthorizeAttribute.PermissionCheckPolicyName, policy => policy.AddRequirements(new PermissionRequirement()));
            });
            services.AddSingleton<Microsoft.AspNetCore.Authorization.IAuthorizationHandler, PermissionAuthorizationHandler>();
            //*********************************************** 
            #endregion

            #region 用户认证

            this.AuthenticationSchemes.Add("DEF_AUTH", _DefaultCookieAuthenticationOptions);

            //*********************自定义Cookie认证**************************
            var authenticationBuilder = services.AddAuthentication(AuthorizationService.DefaultAuthenticationScheme);
            var sd = ServiceDescriptor.Singleton<IPostConfigureOptions<CookieAuthenticationOptions>, PostConfigureCookieAuthenticationOptions>();
            services.TryAddEnumerable(sd);
            var ob = services.AddOptions<CookieAuthenticationOptions>(AuthorizationService.DefaultAuthenticationScheme).Validate(o =>
            o.Cookie.Expiration == null
            , "Cookie.Expiration is ignored, use ExpireTimeSpan instead.");

            foreach (var item in this.AuthenticationSchemes)
            {//注册定义的认证架构到认证服务
                authenticationBuilder.AddScheme<CookieAuthenticationOptions, SharpSoft.Web.Authorize.CookieAuthenticationHandler>(item.Key, item.Value);
            }

            //*****************************************************************************

            //使用自定义的AuthorizationService替换默认的Microsoft.AspNetCore.Authorization.DefaultAuthorizationService
            //用以实现根账号跳过权限检查
            services.AddSingleton<Microsoft.AspNetCore.Authorization.IAuthorizationService, AuthorizationService>();

            #endregion

            #region 页面静态化
            if (ApplyStaticizeService)
            {
                services.AddSingleton<StaticizeService>();
            }
            #endregion

        }
        /// <summary>
        /// 管道配置
        /// </summary>
        /// <param name="app"></param>
        /// <param name="env"></param>
        /// <param name="appLifetime"></param>
        public virtual void Configure(IApplicationBuilder app, IWebHostEnvironment env, IHostApplicationLifetime appLifetime)
        {
            ServiceLocator.Instance = app.ApplicationServices;//全局储存服务容器
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                //自定义异常状态处理页面
                //app.UseStatusCodePagesWithReExecute("/Error/{0}");
                app.UseExceptionHandler("/Home/Error");
                // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
                app.UseHsts();
            }

            if (AllowCrossOrigin)
            {//允许跨域
                app.UseCors(cpb =>
                {
                    if (AllowOrigins == null)
                    {//允许所有源  
                        cpb.SetIsOriginAllowed(ori =>
                        {
                            return true;
                        });
                    }
                    else
                    {//允许特定源
                        cpb.SetIsOriginAllowed(ori =>
                        {
                            return AllowOrigins.Contains(ori);
                        });
                    }
                    cpb.AllowAnyHeader().AllowAnyMethod().AllowCredentials();
                });
                app.Use(async (context, next) =>
                {
                    if (AllowOrigins == null)
                    {//允许所有源  
                        if (context.Request.Headers.ContainsKey("Origin"))
                        {//始终输出允许请求的源跨域访问
                            context.Response.Headers.Add("Access-Control-Allow-Origin", context.Request.Headers["Origin"]);
                        }
                    }
                    await next.Invoke();
                });
            }
            //app.UseResponseCompression();

            app.UseStatusCodePagesWithReExecute("/Home/Error/{0}");//处理服务器
            app.UseExceptionHandler("/Home/Error/500");//处理服务器错误显示

            app.UseWebSockets(new WebSocketOptions() { KeepAliveInterval = TimeSpan.FromSeconds(120), ReceiveBufferSize = 4 * 1024 });

            app.UseSession();

            app.UseStaticFiles();
            app.UseRouting();
            app.UseAuthentication();
            app.UseAuthorization();//The call to app.UseAuthorization() must appear between app.UseRouting() and app.UseEndpoints(...)

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllerRoute(name: "areaRoute",
                    pattern: "{area:exists}/{controller=Home}/{action=Index}/{id?}",
                    defaults: new { action = "Index" });
                endpoints.MapRazorPages();
            });
            app.UseMvcWithDefaultRoute();
        }

        //public virtual void MapRoute(IRouteBuilder routes)
        //{

        //}


    }
}
